

#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>

#import <CoreFoundation/CoreFoundation.h>

#import <CoreTelephony/CTTelephonyNetworkInfo.h>

#import "XMSNetWorkReachability.h"

#define kShouldPrintReachabilityFlags 1

NSString *const XMSNetWorkReachabilityStatusChangedNotification = @"XMSNetWorkReachabilityStatusChangedNotification";

static void PrintReachabilityFlags(SCNetworkReachabilityFlags    flags, const char* comment)
{
#if kShouldPrintReachabilityFlags
	
    NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n",
			(flags & kSCNetworkReachabilityFlagsIsWWAN)				  ? 'W' : '-',
			(flags & kSCNetworkReachabilityFlagsReachable)            ? 'R' : '-',
			
			(flags & kSCNetworkReachabilityFlagsTransientConnection)  ? 't' : '-',
			(flags & kSCNetworkReachabilityFlagsConnectionRequired)   ? 'c' : '-',
			(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)  ? 'C' : '-',
			(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
			(flags & kSCNetworkReachabilityFlagsConnectionOnDemand)   ? 'D' : '-',
			(flags & kSCNetworkReachabilityFlagsIsLocalAddress)       ? 'l' : '-',
			(flags & kSCNetworkReachabilityFlagsIsDirect)             ? 'd' : '-',
			comment			);
#endif
}

@interface XMSNetWorkReachability ()
{
    BOOL _localWiFiRef;
	SCNetworkReachabilityRef _reachabilityRef;
    NSObject *_changednotiblock;
}
@end

@implementation XMSNetWorkReachability

static XMSNetWorkReachability* s_reachability;

static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
    @autoreleasepool
    {
        XMSNetWorkReachability* noteObject = (XMSNetWorkReachability*) info;
        
        [[NSNotificationCenter defaultCenter] postNotificationName:XMSNetWorkReachabilityStatusChangedNotification object: noteObject];
    }
}

- (BOOL) startNotifier
{
    BOOL retVal = NO;
    
    SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
    if(SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
    {
        if(SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, [[NSRunLoop mainRunLoop] getCFRunLoop], kCFRunLoopDefaultMode))
        {
            retVal = YES;
        }
    }    
    
    return retVal;
}

- (void) stopNotifier
{
    if(_reachabilityRef!= NULL)
        SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, [[NSRunLoop mainRunLoop] getCFRunLoop], kCFRunLoopDefaultMode);
}

-(id)init
{
    self=[super init];
    
    return self;
}

- (void) dealloc
{
	[self stopNotifier];
	if(_reachabilityRef!= NULL)
		CFRelease(_reachabilityRef);
    [[NSNotificationCenter defaultCenter] removeObserver:self name:XMSNetWorkReachabilityStatusChangedNotification object:self];
	superdealloc_mm;
}

+ (XMSNetWorkReachability*) reachabilityWithHostName: (NSString*) hostName;
{
	XMSNetWorkReachability* retVal = NULL;
	SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
	if(reachability!= NULL)
	{
		retVal= [[[self alloc] init] autorelease_mm];
		if(retVal!= NULL)
		{
			retVal->_reachabilityRef = reachability;
			retVal->_localWiFiRef = NO;
		}
	}
	return retVal;
}

+ (XMSNetWorkReachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;
{
	SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
	XMSNetWorkReachability* retVal = NULL;
	if(reachability!= NULL)
	{
		retVal= [[[self alloc] init] autorelease_mm];
		if(retVal!= NULL)
		{
			retVal->_reachabilityRef = reachability;
			retVal->_localWiFiRef = NO;
		}
	}
	return retVal;
}

+(XMSNetWorkReachability*)reachabilityWithAddressIP:(NSString*)ip port:(NSUInteger)port
{
	struct sockaddr_in sockaddr;
	memset(&sockaddr,0,sizeof(sockaddr));
	sockaddr.sin_len=sizeof(sockaddr);
	sockaddr.sin_family=AF_INET;
	if(ip!=nil)
	{
		sockaddr.sin_addr.s_addr=inet_addr([ip UTF8String]);
		sockaddr.sin_port=port;
	}
	return [XMSNetWorkReachability reachabilityWithAddress:&sockaddr];
}

+ (XMSNetWorkReachability*) reachabilityForInternetConnection;
{
	struct sockaddr_in zeroAddress;
	bzero(&zeroAddress, sizeof(zeroAddress));
	zeroAddress.sin_len = sizeof(zeroAddress);
	zeroAddress.sin_family = AF_INET;
	return [self reachabilityWithAddress: &zeroAddress];
}

+ (XMSNetWorkReachability*) reachabilityForLocalWiFi;
{
	struct sockaddr_in localWifiAddress;
	bzero(&localWifiAddress, sizeof(localWifiAddress));
	localWifiAddress.sin_len = sizeof(localWifiAddress);
	localWifiAddress.sin_family = AF_INET;
	// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
	localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
	XMSNetWorkReachability* retVal = [self reachabilityWithAddress: &localWifiAddress];
	if(retVal!= NULL)
	{
		retVal->_localWiFiRef = YES;
	}
	return retVal;
}

+ (XMSNetWorkAccessType)currentNetworkAccessType
{
    return [s_reachability currentNetworkAccessType];
}

- (XMSNetWorkReachability*) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags
{
//	PrintReachabilityFlags(flags, "localWiFiStatusForFlags");

	BOOL retVal = XMSNetWorkReachabilityTypeNotReachable;
	if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect))
	{
		retVal = XMSNetWorkReachabilityTypeReachableViaWiFi;
	}
	return retVal;
}

- (XMSNetWorkReachability*) networkStatusForFlags: (SCNetworkReachabilityFlags) flags
{
//	PrintReachabilityFlags(flags, "networkStatusForFlags");
	if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
	{
		// if target host is not reachable
		return XMSNetWorkReachabilityTypeNotReachable;
	}

	XMSNetWorkReachabilityType retVal = XMSNetWorkReachabilityTypeNotReachable;
	
	if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
	{
		// if target host is reachable and no connection is required
		//  then we'll assume (for now) that your on Wi-Fi
		retVal = XMSNetWorkReachabilityTypeReachableViaWiFi;
	}
	
	
	if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
		(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
	{
			// ... and the connection is on-demand (or on-traffic) if the
			//     calling application is using the CFSocketStream or higher APIs

			if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
			{
				// ... and no [user] intervention is needed
				retVal = XMSNetWorkReachabilityTypeReachableViaWiFi;
			}
		}
	
	if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
	{
		// ... but WWAN connections are OK if the calling application
		//     is using the CFNetwork (CFSocketStream?) APIs.
		retVal = XMSNetWorkReachabilityTypeReachableViaWWAN;
        
        if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired )
            retVal = XMSNetWorkReachabilityTypeNotReachable;
        
        /*
         if((flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable)
         {
         if ((flags & kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection)
         {
         retVal = ReachableVia3G;
         if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired)
         {
         retVal = ReachableVia2G;
         }
         }
         }
         */
	}
	return retVal;
}

- (BOOL) connectionRequired;
{
	NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
	SCNetworkReachabilityFlags flags;
	if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
	{
		return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
	}
	return NO;
}

- (XMSNetWorkReachabilityType) currentReachabilityStatus
{
	NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef");
	XMSNetWorkReachabilityType retVal = XMSNetWorkReachabilityTypeNotReachable;
	SCNetworkReachabilityFlags flags;
	if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
	{
		if(_localWiFiRef)
		{
			retVal = [self localWiFiStatusForFlags: flags];
		}
		else
		{
			retVal = [self networkStatusForFlags: flags];
		}
	}
	return retVal;
}

-(void)reachabilityChangedBlock:(void(^)(XMSNetWorkReachability* reachability))block
{
    XMSNetWorkReachability* reachability=self;
    [[NSNotificationCenter defaultCenter] removeObserver:self name:XMSNetWorkReachabilityStatusChangedNotification object:self];
    [[NSNotificationCenter defaultCenter] addObserverForName:XMSNetWorkReachabilityStatusChangedNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note)
    {
        if(note.object==reachability)
            block(reachability);
    }];
}

-(BOOL)isNotReachable
{
    return [self currentReachabilityStatus] == XMSNetWorkReachabilityTypeNotReachable;
}

- (XMSNetWorkAccessType)currentNetworkAccessType
{
    if([self currentReachabilityStatus] == XMSNetWorkReachabilityTypeReachableViaWiFi)
        return XMSNetWorkAccessTypeWifi;
    
    CTTelephonyNetworkInfo *telInfo = [[CTTelephonyNetworkInfo new] autorelease_mm];
    NSString *curreuntAccess = [telInfo currentRadioAccessTechnology];
    if([curreuntAccess isEqualToString:CTRadioAccessTechnologyGPRS] || [curreuntAccess isEqualToString:CTRadioAccessTechnologyEdge] )
        return XMSNetWorkAccessType2G;
    if([curreuntAccess isEqualToString:CTRadioAccessTechnologyWCDMA] || [curreuntAccess isEqualToString:CTRadioAccessTechnologyCDMA1x] ||
       [curreuntAccess isEqualToString:CTRadioAccessTechnologyHSDPA] || [curreuntAccess isEqualToString:CTRadioAccessTechnologyHSUPA])
        return XMSNetWorkAccessType3G;
    if([curreuntAccess isEqualToString:CTRadioAccessTechnologyLTE])
        return XMSNetWorkAccessType4G;
    
    return XMSNetWorkAccessTypeNone;
}


+(void)initReachability:(XMSNetWorkReachability*(^)())init
{
    if(s_reachability==nil)
        s_reachability=[init() retain_mm];
}

+(XMSNetWorkReachability*)reachability
{
    return s_reachability;
}

+(void)releaseReachability
{
    [s_reachability release_mm];
    s_reachability=nil;
}

@end
